package profiling;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;



public aspect MLP_Profiler {
	private final Map<String, Long> methodTimes = new HashMap<String, Long>();
	
	private String topLevelMethod = "StaticPartImpl.main";
	private static final Logger log = Logger.getLogger(MLP_Profiler.class.getName());
	
	pointcut sparse() : execution(* *(..)) && !execution(* main(..)) && !within(MLP_Profiler);
	
	pointcut run() : execution(* main(..));
	
	void around() : run() {
		final long start, end;
		start = System.nanoTime();
		proceed();
		end = System.nanoTime();
		this.methodTimes.put(getJoinPointName(thisJoinPoint), ((end - start) / 1000) / 1000);
	}
	
	public static <K, V extends Comparable<V>> Map<K, V> sortByValues(final Map<K, V> map) {
		Comparator<K> valueComparator =  new Comparator<K>() {
		    public int compare(K k1, K k2) {
		        int compare = map.get(k2).compareTo(map.get(k1));
		        return compare;
		    }
		};
		Map<K, V> sortedByValues = new TreeMap<K, V>(valueComparator);
		sortedByValues.putAll(map);
		return sortedByValues;
	}
	
	after() : run() {
		if (this.topLevelMethod.equals(this.getJoinPointName(thisJoinPoint)) == false) return;
		
		Map<String, Long> sortedMap = sortByValues(methodTimes);
		
		for (final String method : sortedMap.keySet()) {
			if (method.equals(this.topLevelMethod)) continue;
			final long methodTime = methodTimes.get(method) / 1000 / 1000;
			System.out.println("Method: " + method + "() took " + methodTime );
		}
	}
	
	Object around(): sparse() {
		final long start, end, executionTime;
		start = System.nanoTime();
		Object result = proceed();
		end = System.nanoTime();
		executionTime = end - start;
		
		String key = getJoinPointName(thisJoinPoint);
		synchronized(this.methodTimes) {
			if(methodTimes.containsKey(key)) {
				this.methodTimes.put(key, executionTime + methodTimes.get(key));
			} else {
				this.methodTimes.put(key, executionTime);
			}
		}
		return result;
	}
	
	private final String getJoinPointName(final JoinPoint joinPoint) {
		if(joinPoint.getThis()==null) {
			return joinPoint.getStaticPart().getClass().getSimpleName() + "." + joinPoint.getSignature().getName();
		}
		return joinPoint.getThis().getClass().getSimpleName() + "." + joinPoint.getSignature().getName();
	}
}
